/* Database.cpp

   Function definitions for the Database class.
*/

#include "Database.h"

#include <sstream>

using std::ostringstream;



// Load the database from the given input stream.
void Database::Load(istream &in)
{
    transactions.clear();
    accounts.clear();
    
    Transaction transaction;
    while(in>>transaction)
    {
	const string &from = transaction.GetFromAccount();
	const string &to = transaction.GetToAccount();
	double amount = transaction.GetAmount();

	if(transaction.IsCommand())
	{
	    if(from == "set")
		accounts[to].SetFlag(transaction.GetComment(), amount != 0);
	    else if(from == "type")
		accounts[to].SetType(transaction.GetComment());
	}
	else
	{
	    // Store the existence of both named accounts.
	    accounts[from];
	    accounts[to];
	    // Store the transaction.
	    transactions.push_back(transaction);
	}
    }
}



// Save the database to the given output stream.
void Database::Save(ostream &out) const
{
    // First, save the account flags.
    for(map<string, Account>::const_iterator it = accounts.begin(); it != accounts.end(); ++it)
    {
	// Write the account type unless it is the default type.
	if(it->second.Type() != AccountType())
	    out<<"00000000\t1\ttype\t"<<it->first<<'\t'<<it->second.Type().Name()<<'\n';
	if(it->second.IsChecks())
	    out<<"00000000\t1\tset\t"<<it->first<<"\tChecks\n";
    }

    // Next, save the transactions.
    for(vector<Transaction>::const_iterator it = transactions.begin(); it != transactions.end(); ++it)
	out<<*it;
}




// If the given date contains wildcards, return the sum of all transactions matching the date, not
// including any hidden accounts. If the date is a single day, return the balances of all accounts
// at the beginning of that day, and include the effects of hidden accounts.
const map<string, Account> &Database::Query(const Date &date)
{
    // Start by resetting the balances of all accounts.
    for(map<string, Account>::iterator it = accounts.begin(); it != accounts.end(); ++it)
	it->second.Clear();

    bool allowVirtual = date.IsSingleDay();

    // Process all transactions that match the given date.
    for(vector<Transaction>::const_iterator it = transactions.begin(); it != transactions.end(); ++it)
    {
	if(!date.Includes(it->GetDate()))
	    continue;

	map<string, Account>::iterator fromIt = accounts.find(it->GetFromAccount());
	map<string, Account>::iterator toIt = accounts.find(it->GetToAccount());

	if((fromIt == accounts.end()) | (toIt == accounts.end()))
	    continue;
	
	Account &from = fromIt->second;
	Account &to = toIt->second;

	if(!allowVirtual & ((from.Type() == AccountType::VIRTUAL) | (to.Type() == AccountType::VIRTUAL)))
	    continue;

	double amount = it->GetAmount();
	from -= amount;
	to += amount;
    }

    return accounts;
}



// Get a listing of all checks that have been drawn but not cashed.
const map<string, Account> &Database::Checks()
{
    // For every transaction to or from a "Drawn Checks" account, add to a separate account called
    // "<Drawn Checks Account Name> :: <Check Comments>".
    static map<string, Account> checks;
    checks.clear();

    for(vector<Transaction>::const_iterator it = transactions.begin(); it != transactions.end(); ++it)
    {
	map<string, Account>::iterator fromIt = accounts.find(it->GetFromAccount());
	map<string, Account>::iterator toIt = accounts.find(it->GetToAccount());

	if((fromIt == accounts.end()) | (toIt == accounts.end()))
	    continue;
	
	Account &from = fromIt->second;
	Account &to = toIt->second;

	if(!from.IsChecks() & !to.IsChecks())
	    continue;

	double amount = it->GetAmount();

	if(from.IsChecks())
	    checks[it->GetFromAccount() + " :: " + it->GetComment()] -= amount;
	else
	    checks[it->GetToAccount() + " :: " + it->GetComment()] += amount;
    }

    return checks;
}



// Get a reference to all accounts. The balance values are not valid.
const map<string, Account> &Database::Accounts()
{
    return accounts;
}



// List all transactions matching the given date.
const vector<Transaction> &Database::Transactions(const Date &date)
{
    static vector<Transaction> matches;
    matches.clear();

    for(vector<Transaction>::const_iterator it = transactions.begin(); it != transactions.end(); ++it)
	if(date.Includes(it->GetDate()))
	    matches.push_back(*it);

    return matches;
}



// Get all transactions.
const vector<Transaction> &Database::Transactions()
{
    return transactions;
}



// Add a transaction to the database. The "from" and "to" values may be partial strings that should
// be matched to existing account names. This function will _not_ create new accounts. The return value
// is a string reporting which accounts were determined to be the best match.
const string &Database::AddTransaction(const Transaction &transaction)
{
    transactions.push_back(transaction);
    
    ostringstream out;
    out<<"Added:\n";
    transactions.back().PrettyPrint(out);
    out<<'\n';

    status = out.str();
    return status;
}



// Cancel the most recently added transaction.
const string &Database::CancelTransaction()
{
    ostringstream out;
    out<<"Removed:\n";
    transactions.back().PrettyPrint(out);
    out<<'\n';

    transactions.pop_back();

    status = out.str();
    return status;
}



// Add an account. Newly created accounts always have a balance of 0. The return value is a string
// reporting whether the account was created successfully (it will not be if it already exists).
const string &Database::AddAccount(const string &account)
{
    if(accounts.find(account) != accounts.end())
	status = "Account \"" + account + "\" already exists.\n";
    else
    {
	status = "Created account \"" + account + "\".\n";
	accounts[account];
    }
    
    return status;
}



// Set or clear a flag on an account. The return value is a string giving the account's flags.
// If the flag string is empty, this reports all flags for the account.
// If the string begins with a !, the flag is cleared instead of being set.
const string &Database::SetFlag(const string &account, const string &flag, bool set)
{
    ostringstream out;
    
    map<string, Account>::iterator it = accounts.find(account);
    if(it == accounts.end())
	out<<"There is no account named \""<<account<<"\".\n";
    else
    {
	it->second.SetFlag(flag, set);
	out<<it->first<<' ';
	it->second.PrintInfo(out);
	out<<'\n';
    }

    status = out.str();
    return status;
}



// Set the account type.
const string &Database::SetType(const string &account, const string &type)
{
    ostringstream out;
    
    AccountType accountType(type);
    if(accountType.Name() != type)
	out<<"\""<<type<<"\" is not a valid account type.\n";
    else
    {
	map<string, Account>::iterator it = accounts.find(account);
	if(it == accounts.end())
	    out<<"There is no account named \""<<account<<"\".\n";
	else
	{
	    it->second.SetType(accountType);
	    out<<it->first<<' ';
	    it->second.PrintInfo(out);
	    out<<'\n';
	}
    }

    status = out.str();
    return status;
}
